package com.antler.luajavalibrary.utils;

import android.util.Log;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import static java.lang.Runtime.getRuntime;

public class ShellUtils {
    private final static String TAG = "antler_"+ShellUtils.class.getSimpleName();

    public static final String COMMAND_EXIT = "exit\n";
    public static final String COMMAND_LINE_END = "\n";
    public static final String COMMAND_SPACE = " ";
    public static final String COMMAND_SH = "sh";
    public static final String COMMAND_SU = "su";

    public static class CommandResult {
        public int result = -1;
        public String successMsg;
        public String errorMsg;

        public CommandResult(int result) {
            this.result = result;
        }

        public CommandResult(int result, String successMsg, String errorMsg) {
            this.result = result;
            this.successMsg = successMsg;
            this.errorMsg = errorMsg;
        }
    }

    private static class CommandTimeoutThread extends Thread {
        private Process process;
        private int timeoutMS;

        public CommandTimeoutThread(Process p, int timeoutMS) {
            this.process = p;
            this.timeoutMS = timeoutMS;
        }

        public void run() {
            try {
                sleep((long) (this.timeoutMS));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private ShellUtils() {
        throw new AssertionError();
    }

    public static boolean checkRootPermission() {
        return execCommand("echo root", true, false, 0).result == 0;
    }

    public static CommandResult execCommand(String command, boolean isRoot, int timeoutMS) {
        return execCommand(new String[]{command}, isRoot, true, timeoutMS);
    }

    public static CommandResult execCommand(List<String> commands, boolean isRoot, int timeoutMS) {
        return execCommand(commands == null ? null : (String[]) commands.toArray(new String[0]), isRoot, true, timeoutMS);
    }

    public static CommandResult execCommand(String[] commands, boolean isRoot, int timeoutMS) {
        return execCommand(commands, isRoot, true, timeoutMS);
    }

    public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg, int timeoutMS) {
        return execCommand(new String[]{command}, isRoot, isNeedResultMsg, timeoutMS);
    }

    public static CommandResult execCommand(List<String> commands, boolean isRoot, boolean isNeedResultMsg, int timeoutMS) {
        return execCommand(commands == null ? null : (String[]) commands.toArray(new String[0]), isRoot, isNeedResultMsg, timeoutMS);
    }

    public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg, int timeoutMS) {
        int result = -1;
        if (commands == null || commands.length == 0) {
            return new CommandResult(result, null, null);
        }

        Process process = null;

        DataOutputStream os = null;

        String successMsg = null;
        String errorMsg = null;

        try {
            process = getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);

            os = new DataOutputStream(process.getOutputStream());

            for (String command : commands) {
                if (command == null) {
                    continue;
                }

                os.write(command.getBytes());
                os.writeBytes(COMMAND_SPACE);
            }

            os.writeBytes(COMMAND_LINE_END);
            os.flush();

            os.writeBytes(COMMAND_EXIT);
            os.flush();

            CommandTimeoutThread tCommandTimeout = null;
            if (timeoutMS > 0) {
                tCommandTimeout = new CommandTimeoutThread(process, timeoutMS);
                tCommandTimeout.start();
            }

            if (isNeedResultMsg) {
                final BufferedReader successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
                final StringBuilder sbSuccessMsg = new StringBuilder();
                final Thread tInput = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        String line;
                        try {
                            while ((line = successResult.readLine()) != null) {
                                line += COMMAND_LINE_END;
                                sbSuccessMsg.append(line);
                            }
                        } catch (Exception e) {
                            Log.i(TAG, "read InputStream exception:" + e.toString());
                        } finally {
                            try {
                                successResult.close();
                                Log.i(TAG, "read InputStream over");
                            } catch (Exception e) {
                                Log.i(TAG, "close InputStream exception:" + e.toString());
                            }
                        }
                    }
                });
                tInput.start();

                final BufferedReader errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                final StringBuilder sbErrorMsg = new StringBuilder();
                final Thread tError = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        String line;
                        try {
                            while ((line = errorResult.readLine()) != null) {
                                line += COMMAND_LINE_END;
                                sbErrorMsg.append(line);
                            }
                        } catch (Exception e) {
                            Log.i(TAG, "read ErrorStream exception:" + e.toString());
                        } finally {
                            try {
                                errorResult.close();
                                Log.i(TAG, "read ErrorStream over");
                            } catch (Exception e) {
                                Log.i(TAG, "close ErrorStream exception:" + e.toString());
                            }
                        }
                    }
                });
                tError.start();

                try {
                    tInput.join();
                    tError.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                successMsg = sbSuccessMsg.toString();
                errorMsg = sbErrorMsg.toString();
            }
			
			if (tCommandTimeout != null) {
                tCommandTimeout.join();
            }

            result = process.waitFor();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (process != null) {
                process.destroy();
            }
        }

        return new CommandResult(result, successMsg, errorMsg);
    }
}